package components;

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;



public class MLNeuron extends AbstractNeuron {

	
	/* ****************************************************
	 * 					PARAMETERS
	 **************************************************** */

	private final double V1 = -1.2;
	
	private final double V2 = 18;
	
	private double V3 = 12;//12, 2
	
	private double V4 = 17.4;//17, 30
	
	private double g_Ca = 4.0;//4.0, 4.4
	
	private double g_K = 8.0;
	
	private final double g_L = 2.0;
	
	private double g_K_Ca = 1.0; //1.0, 0.75, 0.25
	
	private final double V_K = -84;
	
	private final double V_L = -60;
	
	private final double V_Ca = 120;
	
	private double phi = 0.066667;//0.04, 1/15, 0.23
	
	private double epsilon = 0.0005; //0.0005, 0.002, 0.005
	
	private double mu = 0.025; //0.025, 0.3, 0.2
	
//	private final double Ca0 = 10;
	
	private double threshold = 20;
	
	private double C = 20;
	
//	private double preferredISI = RandStream.nextGaussian(15, 2.5, 5, 30, true);
	
	/* ****************************************************
	 * 					VARIABLES
	 **************************************************** */
	
	private double memPotential = -60;
	
	private double I_bg = 40;
	
	private double I_syn = 0.0;
	
	private double I_K;
	
	private double I_L;
	
	private double I_Ca;
	
	private double I_K_Ca;
	
	private double n = 0.014173;
	
	private double Ca = 0;
	
	private double delta_n;
	
	private double delta_Ca;
	
	private double delta_V;
	
	private double dv_previous = 0;
	
	boolean hmP = true;
	
	boolean dead = false;
	
	
	public MLNeuron(boolean excitatory, int index) {
		super(excitatory, index);
		setTimeConstant(30);
		
	}
	
	private double m_function (double V) {
		double i = 0.5 * (1 + Math.tanh((memPotential - V1)/V2));
		if(Double.isNaN(i)) {
			System.out.println("Nan in m_func.");
			System.exit(1);
		} 
		return i;
		
	}
	
	private double n_function (double V) {
		double i = 0.5 * ( 1 + Math.tanh( (memPotential - V3) /V4) );
		if(Double.isNaN(i)) {
			System.out.println("Nan in n_func.");
			System.out.println("mp: " + memPotential);
			System.out.println("n: " + n);
			System.out.println("dn: " + delta_n);
			System.out.println("Isyn: " + I_syn);
			System.exit(1);
		} 
		return i;
	}
	
	private double tau_n_function(double V) {
		double i = 1/Math.cosh( (memPotential-V3)/(2 * V4) );
		if(Double.isNaN(i)) {
			throw new RuntimeException("Nan in tau_func");
		}
		return i;
	}
	
	
	public boolean partialUpdate(double timeStep) {
		if(!dead) {
		I_syn = 0;
		
//		if(memPotential > 10000){
//			dead = true;
//			System.out.println("Neuron " + getIndex() + " has died. +");
//			return false;
//		}
//		if(memPotential < -10000){
//			dead = true;
//			System.out.println("Neuron " + getIndex() + " has died. -");
//			return false;
//		}
		
//		if(memPotential > 500 || memPotential < -500){
//			g_K += 8.0;
//		}
		
		if (getFanIn() != null) {	
			for (int i = 0, n = getFanIn().size(); i < n; i++) {
				Synapse s = getFanIn().get(i);
					I_syn += s.getPSR();
				s.update(timeStep);
			}
		}
		
		if(tau_n_function(memPotential) == 0) {
			System.out.println(memPotential);
			throw new RuntimeException("Tau function returns zero.");
		}
		
		delta_n   = timeStep * phi * ((n_function(memPotential) - n) /
						tau_n_function(memPotential));	
		
//		delta_Ca  = timeStep * epsilon * ((-mu*g_Ca*m_function(memPotential) *
//				(memPotential - V_Ca)) - Ca);
		
		delta_Ca  = timeStep * epsilon * (-mu*I_Ca - Ca);
		
		I_Ca    = g_Ca * m_function(memPotential) * (memPotential - V_Ca);
		I_K     = g_K * n * (memPotential - V_K);
		I_L     = g_L * (memPotential - V_L);
		I_K_Ca  = g_K_Ca * (Ca/(Ca + 1)) * (memPotential - V_K);
//		I_K_Ca  = g_K_Ca * (1 - (Ca0/(Ca0 + Ca))) * (memPotential - V_K);
		
		delta_V = timeStep * (I_syn + I_bg - I_Ca - I_K - I_L - I_K_Ca) / C;
		
		memPotential = memPotential + delta_V;
		n = n + delta_n;
		Ca = Ca + delta_Ca;
		
		
		boolean spiked;
		if(memPotential > threshold &&
				(delta_V < 0 && dv_previous >= 0)) {
			spiked = true;
//			System.out.println("Spike: " + time + " " +  getIndex());
		} else {
			spiked = false;
			setTime(time + timeStep);
		}	
		
		dv_previous = delta_V;
			
		return spiked;		
		} else {
			return false;
		}
		
	}
	
	@Override
	protected void spike() {
		setLastSpike(getTime()); //Set now as last time we spiked
//		System.out.println("Spike: " + getIndex() + " " + getTime());
		if(isReporter()) {
			getRecorder().spikeEvent(getIndex());
		}
		//ItepreferredISI over efferent synapses...
		for (int i = 0, n = getFanOut().size(); i < n; i++) {
			getFanOut().get(i).charge(getTime());
		}		
		
		if(isStdpOn()) {
			getStdp().applySTDPRule(getFanOut(), getTime(), false);
			getStdp().applySTDPRule(getFanIn(), getTime(), true);
		}
		
	}
	
	/**
	 * 
	 * @param timeStep
	 */
	public void spike(double timeStep) {
		spike();
		setTime(time + timeStep);
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MLNeuron neuron = new MLNeuron(true, 0);
		
		double ts = 0.1;
		neuron.setI_bg(0);
		for(double i = 0; i < 10000; i+=ts) {

			neuron.partialUpdate(ts);
		}
		
		try {
			FileWriter fw = new FileWriter("MLData.dat");
			PrintWriter pw = new PrintWriter(fw);
			
			double [] v = new double[(int)(10000/ts)];
			double [] k = new double[(int)(10000/ts)];
			double [] ca = new double[(int)(10000/ts)];
			double [] dv = new double[(int)(10000/ts)];
			int spkCount = 0;
//			int interval = 0;
			int pooper = 0;
			for(int i = 0; i < 100000; i++) {
//				if(i > 10000 && i < 30000) {
//					neuron.setI_bg(0);
//				}
				

				
//				if(i == 1000 + interval || i == 1002 + interval){
//					neuron.setI_bg(3400);
//					pooper ++;
//				}
//				if(i == 1003 + interval || i == 1004 + interval){
//					neuron.setI_bg(-5000);
//				}
//				if(i == 1005 + interval || i == 1006 + interval){
//					neuron.setI_bg(1600);
//				}
//				if(i == 1007 + interval || i == 1008 + interval){
//					neuron.setI_bg(45);
//				}
//				
//				if(i < 90000 && i > 1000){
//					if(i % 300 == 0)
//						interval += 300;
//				}
				
				
				if(i > 20000 && i < 60000) {
					neuron.setI_bg(70);
				}
				
				if(i > 60000) {
					neuron.setI_bg(0);
				}
				
//				if(i > 20000 && i < 40000) {
//					neuron.setI_bg(45);
//				}
//				
//				if(i > 40000 && i < 50000) {
//					neuron.setI_bg(0);
//				}
//				
//				if(i > 50000 && i < 60000) {
//					neuron.setI_bg(70);
//				}
//				
//				if(i > 60000) {
//					neuron.setI_bg(0);
//				}
				
				boolean ted = neuron.partialUpdate(ts);
				if(ted)
					spkCount++;
				v[i] = neuron.getMemPotential();
				k[i] = neuron.getN();
				ca[i] = neuron.getCa();
				dv[i] = neuron.getDelta_V();
			}
			System.out.println(pooper);
			for(int i = 0; i < 100000; i++) {
				pw.print(v[i] + " ");
			}
			pw.println();
	
			for(int i = 0; i < 100000; i++) {
				pw.print(k[i] + " ");
			}
			pw.println();
			
			for(int i = 0; i < 100000; i++) {
				pw.print(ca[i] + " ");
			}
			pw.println();

			for(int i = 0; i < 100000; i++) {
				pw.print(dv[i] + " ");
			}
			
			pw.close();
			
			System.out.println(spkCount);
			
		} catch (IOException ie) {
			ie.printStackTrace();
		}
		
		

	}

	@Override
	public boolean update(double timeStep) {
		// TODO Auto-generated method stub
		return false;
	}



	public double getThreshold() {
		return threshold;
	}

	public void setThreshold(double threshold) {
		this.threshold = threshold;
	}

	public double getMemPotential() {
		return memPotential;
	}

	public void setMemPotential(double memPotential) {
		this.memPotential = memPotential;
	}

	public double getI_bg() {
		return I_bg;
	}

	public void setI_bg(double i_bg) {
		I_bg = i_bg;
	}

	public double getI_syn() {
		return I_syn;
	}

	public void setI_syn(double i_syn) {
		I_syn = i_syn;
	}

	public double getI_K() {
		return I_K;
	}

	public void setI_K(double i_K) {
		I_K = i_K;
	}

	public double getI_L() {
		return I_L;
	}

	public void setI_L(double i_L) {
		I_L = i_L;
	}

	public double getI_Ca() {
		return I_Ca;
	}

	public void setI_Ca(double i_Ca) {
		I_Ca = i_Ca;
	}

	public double getI_K_Ca() {
		return I_K_Ca;
	}

	public void setI_K_Ca(double i_K_Ca) {
		I_K_Ca = i_K_Ca;
	}

	public double getN() {
		return n;
	}

	public void setN(double n) {
		this.n = n;
	}

	public double getCa() {
		return Ca;
	}

	public void setCa(double ca) {
		Ca = ca;
	}

	public double getDelta_n() {
		return delta_n;
	}

	public void setDelta_n(double delta_n) {
		this.delta_n = delta_n;
	}

	public double getDelta_Ca() {
		return delta_Ca;
	}

	public void setDelta_Ca(double delta_Ca) {
		this.delta_Ca = delta_Ca;
	}

	public double getDelta_V() {
		return delta_V;
	}

	public void setDelta_V(double delta_V) {
		this.delta_V = delta_V;
	}

	public double getV1() {
		return V1;
	}

	public double getV2() {
		return V2;
	}

	public double getV3() {
		return V3;
	}

	public double getV4() {
		return V4;
	}

	public double getG_Ca() {
		return g_Ca;
	}

	public double getG_K() {
		return g_K;
	}

	public double getG_L() {
		return g_L;
	}

	public double getG_K_Ca() {
		return g_K_Ca;
	}

	public double getV_K() {
		return V_K;
	}

	public double getV_L() {
		return V_L;
	}

	public double getV_Ca() {
		return V_Ca;
	}

	public double getPhi() {
		return phi;
	}

	public double getEpsilon() {
		return epsilon;
	}

	public void setEpsilon(double epsilon) {
		this.epsilon = epsilon;
	}

	public double getMu() {
		return mu;
	}

	public void setMu(double mu) {
		this.mu = mu;
	}

	public void setV3(double v3) {
		V3 = v3;
	}

	public void setV4(double v4) {
		V4 = v4;
	}

	public void setG_Ca(double g_Ca) {
		this.g_Ca = g_Ca;
	}

	public void setG_K_Ca(double g_K_Ca) {
		this.g_K_Ca = g_K_Ca;
	}

	public void setPhi(double phi) {
		this.phi = phi;
	}

	public double getC() {
		return C;
	}

	public void setC(double c) {
		C = c;
	}

}
